/*******************************************************************************
*  file    : solutions.cpp
*  created : 15.07.2014
*  author  : Slyshyk Oleksiy (alexSlyshyk@gmail.com)
*******************************************************************************/

#include "solutions.hpp"
#include <QDir>
#include <QTimer>
#include <QPluginLoader>
#include <QCoreApplication>

#include <QFile>
#include <QTextStream>

#include <QtConcurrent/QtConcurrentRun>
#include <QMutexLocker>
#include <QFuture>

#include <qlogger.hpp>
#include <interfaces.hpp>
#include <dataobject/dataobject.hpp>

#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext.hpp>
#include "utils.hpp"

namespace rng = boost::range ;

namespace
{
QLogger * l = QLoggerEngine::getLogger("Core");
QLogger * s = QLoggerEngine::getLogger("Solution");
}

Solutions::Solutions(QObject *parent) :
    QObject(parent)
{

    report_.reserve(100);
    wind_speeds_.reserve(15);


    speeds_        .set_capacity(10);
    data_solutions_.set_capacity(10*14);
    ma_speeds_     .set_capacity(10*14);
    ma2_speeds_    .set_capacity(10*14);
    speeds_for_ma_ .set_capacity(15);
    speeds_for_ma2_.set_capacity(15);


    report_timer_ = new QTimer(this);
    connect(report_timer_, SIGNAL(timeout()), this, SLOT(saveReport()) );
    report_timer_->start(1000);
}

Solutions::~Solutions()
{
    saveReport();
    for(QFutureWatcher<NamedData*> * w : futureWatcherList_)
        {
            w->cancel();
            w->deleteLater();
        }
}

QStringList Solutions::patternList() const
{

    if(transformPlugins_ && transformPlugins_->keys().size())
        return (*transformPlugins_)[transformPlugins_->keys()[0]]->patternList();

    return QStringList();
}

QStringList Solutions::transformationList() const
{
    QStringList keys;
    if(transformPlugins_)
        keys += transformPlugins_->keys();
    if(transformIntPlugins_)
        keys += transformIntPlugins_->keys();

    std::sort(keys.begin(),keys.end());

    return keys;
}

void Solutions::setParams(const QVariantMap &pulse, const QVariantMap &signal)
{
    par_pulse_  = pulse;
    par_signal_ = signal;
}

const FilteredWindSpeedBuffer &Solutions::speeds() const
{
    return speeds_;
}

const FilteredWindSpeedBuffer &Solutions::ma_speeds() const
{
    return ma_speeds_;
}

const FilteredWindSpeedBuffer&Solutions::ma2_speeds() const
{
    return ma2_speeds_;
}

const DataSolutionBuffer &Solutions::solutions() const
{
    return data_solutions_;
}

DataObjectSolution Solutions::calculateDataObject(DataObjectPtr data_obj, QVariantMap param_signal, QVariantMap param_pulse)
{
    if(transformPlugins_.isNull())
        {
            logError(l) << "Not set transform plugins";
            return DataObjectSolution();
        }

    DataObjectSolution solution;

    if(transformPlugins_->keys().contains(param_signal["transformation"].toString()) &&
       transformPlugins_->keys().contains(param_pulse ["transformation"].toString())    )
        {
            solution = calcData(data_obj->dataL().data(),data_obj->dataR().data(),param_signal,param_pulse);
        }
    else if(transformIntPlugins_->keys().contains(param_signal["transformation"].toString()) &&
            transformIntPlugins_->keys().contains(param_pulse ["transformation"].toString())    )
        {
            NamedIntDataPtr dataL  = NamedIntDataPtr(new NamedIntData(data_obj->dataL().data()));
            NamedIntDataPtr dataR  = NamedIntDataPtr(new NamedIntData(data_obj->dataR().data()));
            solution = calcData(dataL.data(),dataR.data(),param_signal,param_pulse);
        }
    else
        {
            logError(l) << "You must use combatible plugins .";
            return solution;
        }

    solution.temperature = data_obj->temperature();
    solution.pressure    = data_obj->pressure();
    solution.time        = data_obj->time().toMSecsSinceEpoch();
    const double V = plugin_utils::sound_speed(273.15 + base_temp_);
    const double Len_L = V * base_time_L_;
    const double Len_R = V * base_time_R_;
    if(solution.delta_t_signal_L > 0. && solution.delta_t_pulse_L > 0.)
        {
            solution.delta_V_L = ( Len_L / ( solution.delta_t_signal_L - solution.delta_t_pulse_L ) )
                                 - plugin_utils::sound_speed(273.15 + solution.temperature) ;
            last_calc_len_L = plugin_utils::sound_speed(273.15 + solution.temperature) * ( solution.delta_t_signal_L - solution.delta_t_pulse_L);
        }
    if(solution.delta_t_signal_R > 0. && solution.delta_t_pulse_R > 0.)
        {
            solution.delta_V_R = ( Len_R / ( solution.delta_t_signal_R - solution.delta_t_pulse_R ) )
                                 - plugin_utils::sound_speed(273.15 + solution.temperature) ; ;
            last_calc_len_R = plugin_utils::sound_speed(273.15 + solution.temperature) * ( solution.delta_t_signal_R - solution.delta_t_pulse_R);
        }

    double last_dt = 0.;
    if(solution.delta_t_signal_L > 0. && solution.delta_t_pulse_L > 0. && solution.delta_t_signal_R > 0. && solution.delta_t_pulse_R > 0.)
        {
            if( (std::abs(( solution.delta_t_signal_L - solution.delta_t_pulse_L ) - base_time_L_) < 0.0000001) ||
                (std::abs(( solution.delta_t_signal_R - solution.delta_t_pulse_R ) - base_time_R_) < 0.0000001))
                last_dt = 0.;
            else
                {
                    double dtl = 1000.0*solution.delta_t_signal_L - 1000.0*solution.delta_t_pulse_L;
                    double dtr = 1000.0*solution.delta_t_signal_R - 1000.0*solution.delta_t_pulse_R;
                    //FIXME: need change calculation formula to use V compare to L
#warning "Use only Len_L, compare to use both values !"
                    last_dt = 1000.0* Len_L /2.0 *( dtl -dtr );
                }
            solution.delta_V = last_dt ;
        }

    if( !std::isfinite(solution.delta_V))
        solution.delta_V   = 0.0;
    if( !std::isfinite(solution.delta_V_L))
        solution.delta_V_L = 0.0;
    if( !std::isfinite(solution.delta_V_R))
        solution.delta_V_R = 0.0;

    return solution;
}

DataObjectSolution Solutions::calcData(const NamedData* datL,const NamedData* datR, QVariantMap param_signal, QVariantMap param_pulse)
{
    DataObjectSolution solution;
    double lcpt_L;
    double lcpt_R;
    double lcst_L;
    double lcst_R;

    {
        QMutexLocker lock(&last_calk_mutex_);
        lcpt_L = last_calc_pulse_time_L;
        lcst_L = last_calc_time_L;
        lcpt_R = last_calc_pulse_time_R;
        lcst_R = last_calc_time_R;
    }

    if( !transformPlugins_->keys().contains(param_signal["transformation"].toString()))
        {
            logError(l) << "Can't find transform plugin " << param_signal["transformation"].toString();
            return solution;
        }
    if( !transformPlugins_->keys().contains(param_pulse["transformation"].toString()))
        {
            logError(l) << "Can't find transform plugin " << param_pulse["transformation"].toString();
            return solution;
        }

        {
            TransformNamedData* plugin = (*transformPlugins_)[param_signal["transformation"].toString()];
            QVariantMap par = param_signal;
            par["id"          ] = "signal";
            par["pattern_file"] = par["pattern_left_file"];
            par["prev_time"   ] = lcst_L;
            NamedDataPtr dataL  = NamedDataPtr(plugin->transform(datL,par));
            //NamedData* dataL = plugin->transform(datL,par);
            par["pattern_file"] = par["pattern_right_file"];
            par["prev_time"   ] = lcst_R;
            NamedDataPtr dataR  = NamedDataPtr(plugin->transform(datR,par));
            //NamedData* dataR = plugin->transform(datR,par);

            solution.delta_t_signal_L = dataL->pantry["ansver_idx"].toInt() / (double)dataL->sampleRate;
            solution.delta_t_signal_R = dataR->pantry["ansver_idx"].toInt() / (double)dataR->sampleRate;
        }

        {
            TransformNamedData* plugin = (*transformPlugins_)[param_pulse["transformation"].toString()];
            QVariantMap par = param_pulse;
            par["id"          ] = "pulse";
            par["pattern_file"] = par["pattern_left_file"];
            par["prev_time"   ] = lcpt_L;
            NamedDataPtr dataL  = NamedDataPtr(plugin->transform(datL,par));
            //NamedData* dataL = plugin->transform(datL,par);
            par["pattern_file"] = par["pattern_right_file"];
            par["prev_time"   ] = lcpt_R;
            NamedDataPtr dataR  = NamedDataPtr(plugin->transform(datR,par));
            //NamedData* dataR = plugin->transform(datR,par);

            solution.delta_t_pulse_L = dataL->pantry["ansver_idx"].toInt() / (double)dataL->sampleRate;
            solution.delta_t_pulse_R = dataR->pantry["ansver_idx"].toInt() / (double)dataR->sampleRate;
        }

    return solution;
}

DataObjectSolution Solutions::calcData(const NamedIntData *datL, const NamedIntData *datR, QVariantMap param_signal, QVariantMap param_pulse)
{
    DataObjectSolution solution;
    double lcpt_L;
    double lcpt_R;
    double lcst_L;
    double lcst_R;

    {
        QMutexLocker lock(&last_calk_mutex_);
        lcpt_L = last_calc_pulse_time_L;
        lcst_L = last_calc_time_L;
        lcpt_R = last_calc_pulse_time_R;
        lcst_R = last_calc_time_R;
    }

    if( !transformIntPlugins_->keys().contains(param_signal["transformation"].toString()))
        {
            logError(l) << "Can't find transform plugin " << param_signal["transformation"].toString();
            return solution;
        }
    if( !transformIntPlugins_->keys().contains(param_pulse["transformation"].toString()))
        {
            logError(l) << "Can't find transform plugin " << param_pulse["transformation"].toString();
            return solution;
        }

        {
            TransformNamedIntData* plugin = (*transformIntPlugins_)[param_signal["transformation"].toString()];
            QVariantMap par = param_signal;
            par["id"          ] = "signal";
            par["pattern_file"] = par["pattern_left_file"];
            par["prev_time"   ] = lcst_L;
            NamedIntDataPtr dataL  = NamedIntDataPtr(plugin->transform(datL,par));
            par["pattern_file"] = par["pattern_right_file"];
            par["prev_time"   ] = lcst_R;
            NamedIntDataPtr dataR  = NamedIntDataPtr(plugin->transform(datR,par));

            solution.delta_t_signal_L = dataL->pantry["ansver_idx"].toInt() / (double)dataL->sampleRate;
            solution.delta_t_signal_R = dataR->pantry["ansver_idx"].toInt() / (double)dataR->sampleRate;
        }

        {
            TransformNamedIntData* plugin = (*transformIntPlugins_)[param_pulse["transformation"].toString()];
            QVariantMap par = param_pulse;
            par["id"          ] = "pulse";
            par["pattern_file"] = par["pattern_left_file"];
            par["prev_time"   ] = lcpt_L;
            NamedIntDataPtr dataL  = NamedIntDataPtr(plugin->transform(datL,par));
            par["pattern_file"] = par["pattern_right_file"];
            par["prev_time"   ] = lcpt_R;
            NamedIntDataPtr dataR  = NamedIntDataPtr(plugin->transform(datR,par));

            solution.delta_t_pulse_L = dataL->pantry["ansver_idx"].toInt() / (double)dataL->sampleRate;
            solution.delta_t_pulse_R = dataR->pantry["ansver_idx"].toInt() / (double)dataR->sampleRate;
        }

    return solution;
}

void Solutions::onDataObjectReady(DataObjectPtr data)
{
//    data_object_queue_.enqueue(data);
//    if(futureWatcherDataObjectList_.size() == 0)
//        startTransform(data_object_queue_.dequeue());
    startTransform(data);
}

void Solutions::startTransform(DataObjectPtr data)
{
    QFutureWatcher<DataObjectSolution> * watcher = new QFutureWatcher<DataObjectSolution>;
    futureWatcherDataObjectList_.push_back(watcher);
    connect(watcher, SIGNAL(finished()), this, SLOT(computationDataObjectFinished()) );
    watcher->setFuture( QtConcurrent::run(std::bind(&Solutions::calculateDataObject,this,data, par_signal_, par_pulse_) ) );
}

void Solutions::computationDataObjectFinished()
{
    QFutureWatcher<DataObjectSolution> * watcher = dynamic_cast<QFutureWatcher<DataObjectSolution> *>(this->sender());
    if(watcher)
        {
            data_solutions_.push_back(watcher->future().result());
            if(data_solutions_.size() == 1)
                for(size_t i = 0 ; i <= data_solutions_.capacity() ; ++i)
                    data_solutions_.push_back(watcher->future().result());

            auto cmp_sol_by_time = [](const DataObjectSolution& s1, const DataObjectSolution& s2){ return (s1.time < s2.time);};

            if(!rng::is_sorted(data_solutions_,cmp_sol_by_time))
                rng::sort(data_solutions_,cmp_sol_by_time);

            wind_speeds_.push_back(watcher->future().result().delta_V);
            speeds_for_ma_.push_back(watcher->future().result().delta_V);

            {
                QMutexLocker lock(&last_calk_mutex_);
                last_calc_pulse_time_L =  watcher->future().result().delta_t_pulse_L;
                last_calc_time_L       =  watcher->future().result().delta_t_signal_L;
                last_calc_pulse_time_R =  watcher->future().result().delta_t_pulse_R;
                last_calc_time_R       =  watcher->future().result().delta_t_signal_R;
            }

            //Усреднение
            double d = 0.;
            if(wind_speeds_.size() == 14)
                {
                    wind_speeds_.erase(boost::range::min_element(wind_speeds_));
                    wind_speeds_.erase(boost::range::min_element(wind_speeds_));
                    wind_speeds_.erase(boost::range::max_element(wind_speeds_));
                    wind_speeds_.erase(boost::range::max_element(wind_speeds_));

                    for(const auto& s : wind_speeds_)
                        d += s;

                    d = d / wind_speeds_.size();
                    if(std::isnan(d))
                        d = 0.0;

                    wind_speeds_.clear();

                    speeds_.push_back( std::make_pair(data_solutions_.back().time,d) );
                    if(speeds_.size() == 1)
                        for(size_t i = 0 ; i <= speeds_.capacity() ; ++i)
                            speeds_.push_back(std::make_pair(watcher->future().result().time,d));
                    emit windSpeedChanged(watcher->future().result().time,d);
                }

            if(speeds_for_ma_.size() > 5)
            {
                std::vector<double> tmp;
                tmp.resize(speeds_for_ma_.size());
                //std::transform(data_solutions_.begin(),data_solutions_.end(),tmp.begin(),[](const DataObjectSolution& ds){return ds.delta_V;});
                std::copy(speeds_for_ma_.begin(),speeds_for_ma_.end(),tmp.begin());

                tmp.erase(boost::range::min_element(tmp));
                tmp.erase(boost::range::min_element(tmp));
                tmp.erase(boost::range::max_element(tmp));
                tmp.erase(boost::range::max_element(tmp));

                d = 0.;
                for(const auto& v: tmp)
                    d += v;
                d = d / tmp.size();
                ma_speeds_.push_back(std::make_pair(data_solutions_.back().time,d));
                speeds_for_ma2_.push_back(d);
            }
            if(speeds_for_ma2_.size() > 5)
                {
                    std::vector<double> tmp;
                    tmp.resize(speeds_for_ma2_.size());
                    std::copy(speeds_for_ma2_.begin(),speeds_for_ma2_.end(),tmp.begin());

                    tmp.erase(boost::range::min_element(tmp));
                    tmp.erase(boost::range::min_element(tmp));
                    tmp.erase(boost::range::max_element(tmp));
                    tmp.erase(boost::range::max_element(tmp));

                    d = 0.;
                    for(const auto& v: tmp)
                        d += v;
                    d = d / tmp.size();

                    ma2_speeds_.push_back(std::make_pair(data_solutions_.back().time,d));
                }

            watcher->deleteLater();
            futureWatcherDataObjectList_.removeAll(watcher);

            task_count_++;
            if(task_size_ && (task_count_ == task_size_))
                emit taskDone();
        }
}

void Solutions::setResponceTime(double t_l, double t_r)
{
    base_time_L_ = t_l;
    base_time_R_ = t_r;
}

void Solutions::flushData()
{
    data_solutions_.clear();
    speeds_        .clear();
    wind_speeds_   .clear();
}

void Solutions::saveReport()
{
    if(report_.size() == 0)
        return;

    QString file_name = qApp->applicationDirPath() + "/wind_calc_report.csv";

    auto clean_report = [this]()
        {
            for(auto l : report_)
                delete l;
            report_.clear();
        };

    QFile f;
    f.setFileName(file_name);
    if(!f.open(QIODevice::Append | QIODevice::Text))
        {
            logInfo(l) << "Can not open file " << file_name << "  " << f.errorString();
            clean_report();
            return;
        }

    QTextStream out(&f);
    for(auto l : report_)
        out << l->join(";") << endl;

    clean_report();
}

double Solutions::temperature() const
{
    return base_temp_;
}

void Solutions::setTemperature(double value)
{
    base_temp_ = value;
}

void Solutions::setTransformPlugins(const QSharedPointer<QMap<QString, TransformNamedData *> > &plugins)
{
    transformPlugins_ = plugins;
}

void Solutions::setTransformIntPlugins(const QSharedPointer<QMap<QString, TransformNamedIntData *> > &plugins)
{
    transformIntPlugins_ = plugins;
}

void Solutions::setTaskSize(int ts)
{
    task_size_ = ts;
    task_count_ = 0;
}

void Solutions::onDataReady(int id, NamedDataPtr data_ptr)
{
    logDebug(l) << "Receive data " << data_ptr->name;
    startTransform(id, data_ptr);
}

void Solutions::startTransform(int id, NamedDataPtr data_ptr)
{
    dataQueue_ << std::make_pair(id,data_ptr);
    pulse_t_   << std::make_pair(id, -1.);
    signal_t_  << std::make_pair(id, -1.);

    if(transformPlugins_->keys().contains(par_signal_["transformation"].toString()))
        {
            TransformNamedData* plugin = (*transformPlugins_)[par_signal_["transformation"].toString()];
            QVariantMap par = par_signal_;

            par["id"] = QStringList()<<"signal"<<QString::number(id);
            par["prev_time"] = last_calc_time_L;
            QFutureWatcher<NamedData*> * watcher = new QFutureWatcher<NamedData*>() ;
            connect(watcher, SIGNAL(finished()), this, SLOT(computationFinished()) );
            watcher->setFuture( QtConcurrent::run( std::bind(&TransformNamedData::transform,plugin,std::placeholders::_1
                                                             ,std::placeholders::_2),data_ptr.data(),par ) );
            futureWatcherList_.append(watcher);
        }
    if(transformPlugins_->keys().contains(par_pulse_["transformation"].toString()))
        {
            TransformNamedData* plugin = (*transformPlugins_)[par_pulse_["transformation"].toString()];
            QVariantMap par = par_pulse_;

            par["id"] = QStringList()<<"pulse"<<QString::number(id);
            par["prev_time"] = last_calc_pulse_time_L;
            QFutureWatcher<NamedData*> * watcher = new QFutureWatcher<NamedData*>() ;
            connect(watcher, SIGNAL(finished()), this, SLOT(computationFinished()) );
            watcher->setFuture( QtConcurrent::run( std::bind(&TransformNamedData::transform,plugin,std::placeholders::_1
                                                             ,std::placeholders::_2),data_ptr.data(),par ) );
            futureWatcherList_.append(watcher);
        }
}

void Solutions::computationFinished()
{
    QFutureWatcher<NamedData*> * watcher = dynamic_cast<QFutureWatcher<NamedData*> *>(this->sender());

    double delta_t_signal = -1;
    double delta_t_pulse  = -1;
    int    data_id        = -1;
    QString param_name;

    if(watcher)
        {
            QSharedPointer<NamedData> transf_data = QSharedPointer<NamedData>(watcher->future().result());
            QStringList id = transf_data->pantry["id"].toStringList();
            if(id.size() == 2)
                {
                    param_name = id[0];
                    data_id = id[1].toInt();
                }

            auto signal = std::find_if(signal_t_.begin(),signal_t_.end(),
                                       [data_id](const std::pair<int, double>& d)->bool{return (d.first == data_id);});
            auto pulse  = std::find_if(pulse_t_.begin(),pulse_t_.end(),
                                       [data_id](const std::pair<int, double>& d)->bool{return (d.first == data_id);});

            if(param_name == "signal" && (signal != signal_t_.end()))
                (*signal).second = transf_data->pantry["ansver_idx"].toInt() / (double)transf_data->sampleRate;
            if(param_name == "pulse"  && (pulse  != pulse_t_.end() ))
                (*pulse ).second = transf_data->pantry["ansver_idx"].toInt() / (double)transf_data->sampleRate;


            if(signal != signal_t_.end())
                delta_t_signal = (*signal).second;
            if(pulse  != pulse_t_.end() )
                delta_t_pulse  = (*pulse ).second;

            const double V = plugin_utils::sound_speed(273.15 + base_temp_);
            const double L = V * base_time_L_;
            if(delta_t_signal > 0. && delta_t_pulse > 0.)
                {
                    last_calc_pulse_time_L = delta_t_pulse;
                    last_calc_time_L       = delta_t_signal;
                    double delta_V = ( L / ( delta_t_signal - delta_t_pulse ) ) - V ;
                    last_calc_len_L = V * ( delta_t_signal - delta_t_pulse);

                    logInfo(s) << QString("Решение №%1 dV = %2")
                                  .arg(data_id)
                                  .arg(delta_V) ;

                    wind_speeds_.push_back(delta_V);

                    QStringList* line = new QStringList;
                    report_.push_back(line);
                    (*line) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz")
                            << QString::number(V)
                            << QString::number(L)
                            << QString::number(last_calc_len_L)
                            << QString::number(delta_V) ;

                    //Усреднение
                    double d = 0.;
                    if(wind_speeds_.size() == 14)
                        {
                            wind_speeds_.erase(boost::range::min_element(wind_speeds_));
                            wind_speeds_.erase(boost::range::min_element(wind_speeds_));
                            wind_speeds_.erase(boost::range::max_element(wind_speeds_));
                            wind_speeds_.erase(boost::range::max_element(wind_speeds_));

                            for(const auto& s : wind_speeds_)
                                d += s;

                            d = d / wind_speeds_.size();
                            if(std::isnan(d))
                                d = 0.0;

                            wind_speeds_.clear();

                            emit windSpeedChanged(QDateTime::currentDateTime().toMSecsSinceEpoch(),d);
                        }

                    signal_t_.erase(signal);
                    pulse_t_ .erase(pulse);
                    dataQueue_.erase(std::find_if(dataQueue_.begin(),dataQueue_.end(),
                                                  [data_id](const QuieuePair& d)->bool{return (d.first == data_id);}));
                }
            futureWatcherList_.removeOne(watcher);
            watcher->deleteLater();
        }
    else
        {
            qDebug() << "ERRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRROR";
        }
}

